package model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

/**
 * The Map class is the main class dealing with the game board. It contains a
 * list of the continents of the world, which in turn hold lists of their
 * associated territories. This class is used as the updating apparatus being
 * sent back and forth to the server. It also adds and retrives continents. It
 * is updated constantly, as it implements observer, and it is also observable.
 * 
 * @author David Shefchik, Xiaotian Chang
 * 
 * 
 */
@SuppressWarnings("serial")
public abstract class Map extends Observable implements Observer, Serializable {

	/**
	 * this is a list containing all continents in the map.
	 */
	public List<Continent> continents;
	/**
	 * mapCards holds all card still holds by the map. the components will
	 * decrease with the game going on.
	 */
	public List<Card> mapCards;
	private int tradeInValue = 4;
	private List<Player> players;
	private GamePhase phase;
	private LastAction lastAction;

	/**
	 * The constructor takes no arguments and creates a new empty Array List of
	 * continents.
	 * 
	 * @param players
	 *            is a ArrayList which has all players in the game.
	 */
	public Map(List<Player> players) {
		this.players = players;
		continents = new ArrayList<Continent>();
		this.mapCards = new ArrayList<Card>();
		setPhase(GamePhase.SELECTING);
	}

	/**
	 * Generates the types of cards to be put in the deck, then assigns each one
	 * a territory. When complete, it shuffles the list of cards.
	 */
	public void assignCardType() {
		int count = 0;
		Card temp;
		for (Continent tempContinent : continents) {
			for (Territory tempTerritory : tempContinent.listTerritory()) {
				if (count % 3 == 0) {
					temp = new Card(CardType.SOLDIER, tempTerritory);
				} else if (count % 3 == 1) {
					temp = new Card(CardType.HORSE, tempTerritory);
				} else {
					temp = new Card(CardType.CANNON, tempTerritory);
				}
				count++;
				this.mapCards.add(temp);
			}
		}
		Collections.shuffle(mapCards);
	}

	/**
	 * Gets the list of all the players in the game
	 * 
	 * @return all the player in the game in an ArrayList.
	 */
	public List<Player> getPlayerList() {
		return players;
	}

	/**
	 * This method sets the player list in the map class with the values of the
	 * player list that is passed in
	 */
	public void setPlayerList() {
		// taken from constructor
		for (Player temp : players) {
			temp.setMap(this);
		}
	}

	/**
	 * This method automatically and randomly assigns rulers to territories at
	 * the beginning of the game. The GUI selects whether to call this method
	 * based on whether the option is selected at game startup.
	 */
	public void assignRulers() {
		int playerNum = getPlayerList().size();
		int index = 0;
		ArrayList<Territory> territories = allTerritories();

		Collections.shuffle(territories);

		while (!territories.isEmpty()) {
			if (index >= playerNum) {
				index = 0;
			}
			Territory temp = territories.get(0);
			territories.remove(temp);
			temp.setRuler(getPlayerList().get(index));
			temp.addTroop(1);
			getPlayerList().get(index).removeBonus(1);
			index++;
		}
		this.setPhase(GamePhase.PLACING);
		this.setChanged();
		this.notifyObservers();
	}

	/**
	 * Gives a list of all the territories in the map
	 * 
	 * @return ArrayList which has all the Territories in the Map.
	 */
	public ArrayList<Territory> allTerritories() {
		ArrayList<Territory> territories = new ArrayList<Territory>();
		for (Continent tempContinent : listContinents()) {
			// System.out.println(tempContinent.getContinentName() + "\n");
			for (Territory tempTerritory : tempContinent.listTerritory()) {
				// System.out.print(tempTerritory.getTerritoryName() + "	");
				territories.add(tempTerritory);
			}
			// System.out.println("");
		}
		return territories;
	}

	/**
	 * Adds the next card in the list to the given player's collection. Then it
	 * removes it from the map cardList.
	 * 
	 * @param player
	 *            is the player to be given card.
	 */
	public void giveCard(Player player) {
		Card tempCard = this.mapCards.get(0);
		this.mapCards.remove(0);
		player.receiveCard(tempCard);
		this.setChanged();
		this.notifyObservers();
	}

	/**
	 * This method is abstract and takes no methods. It is implemented by
	 * extending the Map class with a specific game map (such as the classic
	 * map).This method then generates the territories and continents, and
	 * assigns neighbors.
	 */
	public abstract void buildMap();

	/**
	 * This method returns an ArrayList of all the continents in the map
	 * 
	 * @return An ArrayList of all the continents in the map.
	 */
	public List<Continent> listContinents() {
		return this.continents;
	}

	private boolean containContinent(String name) {
		if (listContinents().size() == 0) {
			return false;
		}
		for (Continent temp : listContinents()) {
			if (temp.getContinentName() == name) {
				return true;
			}
		}
		return false;
	}

	/**
	 * This enum is used to determine what the current phase of play the game is
	 * in. SELECTING is when the player's are choosing their territories one at
	 * a time. PLACING is after all the territories are occupied and players add
	 * troops to territories they own. PLAYING is when the action actually
	 * starts.
	 * 
	 * @author shefchik
	 * 
	 */
	public enum GamePhase {

		/**
		 * state when the player's are choosing their territories one at a time.
		 */
		SELECTING,
		/**
		 * the state that player is placing bonus troop into territories.
		 */
		PLACING,
		/**
		 * the state that player is playing the game.
		 */
		PLAYING;
	}

	/**
	 * Method takes in a String and looks to find a continent with that name. If
	 * it finds a continent with that name, it returns a reference to that
	 * continent.
	 * 
	 * @param name
	 *            The name of the continent being searched for
	 * @return The continent that was found or null if none was found
	 */
	public Continent getContinent(String name) {
		if (containContinent(name)) {
			for (Continent temp : listContinents()) {
				if (temp.getContinentName() == name) {
					return temp;
				}
			}
		}
		return null;
	}

	/**
	 * This method takes in a continent and adds it to the list of continents
	 * contained in the map. This is only used when the map is first being
	 * built.
	 * 
	 * @param toAdd
	 *            The continent to be added to the list of continents
	 * @return A boolean indicating whether the continent has been successfully
	 *         added
	 */
	public boolean addContinent(Continent toAdd) {
		boolean temp = this.continents.add(toAdd);
		this.setChanged();
		this.notifyObservers();
		return temp;
	}

	/**
	 * Given a color value, this method returns the territory associated with
	 * it.
	 * 
	 * @param rgbValue
	 *            is a value send from the GUI.
	 * @return the territory selected.
	 */
	public Territory getTerritory(int rgbValue) {
		Iterator<Continent> it = continents.iterator();
		while (it.hasNext()) {
			Iterator<Territory> it2 = it.next().listTerritory().iterator();
			while (it2.hasNext()) {
				Territory territory = it2.next();
				if (territory.getColorSignature() == rgbValue) {
					return territory;
				}
			}
		}
		return null;
	}

	/**
	 * This method returns the list of cards remaining in the master list.
	 * 
	 * @return a ArrayList which contains all the Cards map has in a ArrayList.
	 */
	public List<Card> getMapCards() {
		return this.mapCards;
	}

	/**
	 * This method adds the given number of wildcards to the gameCards list.
	 * 
	 * @param wildCardNum
	 *            is the number of wild cards to add.
	 */
	public void addWildCard(int wildCardNum) {
		for (int i = 0; i < wildCardNum; i++) {
			this.mapCards.add(new Card(CardType.WILD, null));
		}
		this.setChanged();
		this.notifyObservers();
	}

	/**
	 * Returns the size of the list of cards.
	 * 
	 * @return the size of cards in the map.
	 */
	public int mapCardSize() {
		return this.mapCards.size();
	}

	/**
	 * Retuns this instance of Map.
	 * 
	 * @return the current map.
	 */
	public Map getMap() {
		return this;
	}

	/**
	 * Returns the list of continents
	 * 
	 * @return A list of all continents
	 */
	public List<Continent> getContinentList() {
		return continents;
	}

	/**
	 * Sets the current value of trading in cards to a given integer
	 * 
	 * @param tradeInValue
	 *            An integer representing how much a trade will be worth
	 */
	public void setTradeInValue(int tradeInValue) {
		this.tradeInValue = tradeInValue;
	}

	/**
	 * Returns an integer representing the current trade in value for a set of
	 * cards
	 * 
	 * @return The current value of the trade-in
	 */
	public int getTradeInValue() {
		return tradeInValue;
	}

	/**
	 * Sets the phase of the game to the given phase
	 * 
	 * @param phase
	 */
	public void setPhase(GamePhase phase) {
		this.phase = phase;
	}

	/**
	 * Returns the current phase of the game
	 * 
	 * @return The current GamePhase
	 */
	public GamePhase getPhase() {
		return phase;
	}

	/**
	 * this method set the lastAction to given value.
	 * 
	 * @param la
	 * @return the lastAction value after this method run.
	 */
	public LastAction setLastAction(LastAction la) {
		this.lastAction = la;
		return this.lastAction;
	}

	/**
	 * @return the lastAction of the game.
	 */
	public LastAction getLastAction() {
		return this.lastAction;
	}

}
